﻿// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
using Byond.AudioMidi;
using Byond.Logging;
using Byond.Midi;
using Utils;
using Utils.CtorSubstitution;
using Utils.Documentation;

namespace Byond.HostObjects
{
	[Doc(
		Category = "Builtin Functions",
		Summary = "Provides base functionality that can be used inside a script. The methods and properties from patch can be called directly.")]
	public class Patch
	{
		#region Empty Patch

		static Patch()
		{
			Empty = new Patch();
		}

		public static Patch Empty { get; private set; }

		#endregion

		[Ignore]
		private readonly Patch _parent;

		[Ignore]
		private readonly string _memberName;

		private readonly bool _registerPatch;

		private readonly RuntimeScope _runtimeScope;
		private readonly PatchBuilder _patchBuilder;
		private readonly IUserLogger _logger;

		protected Patch(
			Patch parent = null, string memberName = null, bool registerPatch = false)
		{
			_parent = parent;
			_memberName = memberName;
			_registerPatch = registerPatch;
		}

		internal void Initialize()
		{
			// TODO: Könnte man auch als "scoped" direkt resolven
			_patchInfo = _runtimeScope.MostFuture.PatchInfo;
			_voiceManager = _runtimeScope.MostFuture.VoiceManager;
			_transportRegistrar = _runtimeScope.MostFuture.TransportRegistrar;
			_keyboardCallbackManager = _runtimeScope.MostFuture.KeyboardCallbackManager;
			_midiCallbackManager = _runtimeScope.MostFuture.MidiCallbackManager;
			_note96CallbackController = _runtimeScope.MostFuture.Note96CallbackController;
			if (_registerPatch)
			{
				_runtimeScope.MostFuture.PatchManager.RegisterPatch(
					this, _parent, _memberName);
			}

			// Auto-Create Sub-Patches
			// TODO: Auch mit Properties?
			GetType()
				.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
				.Where(it => it.FieldType.IsSubclassOf(typeof(Patch)))
				.ForEach(it => it.SetValue(this, Create(it.FieldType, it.Name, true)));
		}

		public virtual void Go() { }

		#region Position and Play State Properties

		private PatchInfo _patchInfo;

		[Doc(Summary = "The current sample rate in Hz (for example: 44100).")]
		public double SampleRate { get { return _patchInfo.SampleRate; } }

		[Doc(Summary = "Indicates if the transport of the host (VST or standalone) is in playing state.")]
		public bool IsPlaying { get { return _patchInfo.IsSongPlaying; } }

		[Doc(Summary = "The current tempo of the host (VST or standalone) in BPM (TODO: genauer).")]
		public double SongTempo { get { return _patchInfo.SongTempo; } }

		[Doc(Summary = "The current transport position.")]
		public Position Pos { get { return _patchInfo.Pos; } }

		#endregion

		#region Audio In / Out

		private readonly IAudio _audio;

		[Doc(Summary = "Pass the given value to the output channel specified by the channelIndex. If there is no physical output available, the method call is ignored.")]
		protected void Out(int channelIndex, double value)
		{
			_audio.SetChannelValue(channelIndex, value);
		}

		[Doc(Summary = "Gets the current value of the specified channel. If there is no physical input available, 0 is returned.")]
		protected double In(int channelIndex)
		{
			return _audio.GetChannelValue(channelIndex);
		}

		#endregion

		private readonly IMidi _midi;

		#region MidiIn

		private MidiCallbackManager _midiCallbackManager;

		[Doc(Summary = "Registers a callback that is called when a note on midi event is received. The NoteOnMessage holds information about the midi message. Use the given CallbackControl to cancel the callback registration.")]
		protected CallbackControl NoteOn(Action<NoteOnMessage, CallbackControl> callback)
		{
			return _midiCallbackManager.NoteOnController.Register(callback);
		}

		[Doc(Summary = "Registers a callback that is called when a note on midi event is received. The NoteOnMessage holds information about the midi message.")]
		protected CallbackControl NoteOn(Action<NoteOnMessage> callback)
		{
			return _midiCallbackManager.NoteOnController.Register(callback);
		}

		[Doc(Summary = "Registers a callback that is called when a note off midi event is received. The NoteOffMessage holds information about the midi message. Use the given CallbackControl to cancel the callback registration.")]
		protected CallbackControl NoteOff(Action<NoteOffMessage, CallbackControl> callback)
		{
			return _midiCallbackManager.NoteOffController.Register(callback);
		}

		[Doc(Summary = "Registers a callback that is called when a note off midi event is received. The NoteOffMessage holds information about the midi message.")]
		protected CallbackControl NoteOff(Action<NoteOffMessage> callback)
		{
			return _midiCallbackManager.NoteOffController.Register(callback);
		}

		[Doc(Summary = "Registers a callback that is called when a control change midi event is received. The ControlChangeMessage holds information about the midi message. Use the given CallbackControl to cancel the callback registration.")]
		protected CallbackControl ControlChange(Action<ControlChangeMessage, CallbackControl> callback)
		{
			return _midiCallbackManager.ControlChangeController.Register(callback);
		}

		[Doc(Summary = "Registers a callback that is called when a control change midi event is received. The ControlChangeMessage holds information about the midi message.")]
		protected CallbackControl ControlChange(Action<ControlChangeMessage> callback)
		{
			return _midiCallbackManager.ControlChangeController.Register(callback);
		}

		#endregion

		#region MidiOut

		[Doc(
			Summary = "Sends a NoteOn midi message to the host.",
			Example = "NoteOn(50, 0, 127")]
		protected void NoteOn(int pitch, int channel = 0, int velocity = 127, int midiDeviceNumber = -1)
		{
			_midi.SendMidiMessage(
				new NoteOnMessage(channel, pitch, (byte)velocity, midiDeviceNumber, _patchInfo.DeltaFrames));
		}

		[Doc(
			Summary = "Sends a NoteOff midi message to the host.",
			Example = "NoteOff(0, 50, 127")]
		protected void NoteOff(int pitch = 0, int channel = 0, int velocity = 0, int midiDeviceNumber = -1)
		{
			_midi.SendMidiMessage(
				new NoteOffMessage(channel, pitch, velocity, midiDeviceNumber, _patchInfo.DeltaFrames));
		}

		[Doc(
			Summary = "Sends a NoteOn midi message to the host. After the given duration, NoteOff is automatically sent by the system.",
			Example = "NoteOnOff(0, 50, 127, Pos.AddNotes(24)")]
		protected void NoteOnOff(int pitch, Position duration, int channel = 0, int velocity = 127, int midiDeviceNumber = -1)
		{
			_midi.SendMidiMessage(new NoteOnMessage(channel, pitch, (byte)velocity, midiDeviceNumber, _patchInfo.DeltaFrames));

			// Schedule the note off event
			FromNow(duration, () => NoteOff(pitch, channel, velocity));
		}

		[Doc(
			Summary = "Sends a ControlChange midi message to the host. See here for the control value: http://www.indiana.edu/~emusic/cntrlnumb.html",
			Example = "CC(0, 20, 127")]
		protected void CC(int control, int value, int channel = 0, int midiDeviceNumber = -1)
		{
			_midi.SendMidiMessage(
				new ControlChangeMessage(channel, (byte)control, (byte)value, midiDeviceNumber, _patchInfo.DeltaFrames));
		}

		#endregion

		#region Random / Prop / Math Helper

		private readonly Random _random = new Random(DateTime.Now.GetHashCode());

		[Doc(Summary = "Returns a random number between 0 (including) and 1 (excluding).")]
		protected double Random()
		{
			return _random.NextDouble();
		}

		[Doc(Summary = "Returns a random number between 0 (including) and the maximum int value.")]
		protected int RandomInt()
		{
			return _random.Next();
		}

		[Doc(Summary = "Returns a random number between 'from' (including) and 'to' (excluding).")]
		protected double Random(double from, double to)
		{
			// Abbildung 0..1 auf from..to
			return (to - from) * _random.NextDouble() + from;
		}

		[Doc(Summary = "Returns a random number between 'from' (including) and 'to' (excluding).")]
		protected int RandomInt(int from, int to)
		{
			return _random.Next(@from, to);
		}

		[Doc(Summary = "Returns a random element from the given array. If the array is empty, 0 is returned.")]
		protected double Pick(params double[] elements)
		{
			// Abbildung 0..1 auf from..to
			return elements.Length == 0
				? 0
				: elements[_random.Next(0, elements.Length)];
		}

		[Doc(Summary = "Returns a random element from the given array. If the array is empty, 0 is returned.")]
		protected int PickInt(params int[] elements)
		{
			// Abbildung 0..1 auf from..to
			return elements.Length == 0
				? 0
				: elements[_random.Next(0, elements.Length)];
		}

		[Doc(Summary = "Returns true in 'propability' % of all cases. Propability is a value between 0.0 and 1.0.")]
		protected bool Prop(double propability)
		{
			var nextDouble = _random.NextDouble();
			return nextDouble < propability;
		}

		[Doc(Summary = "Returns true in 'propability' % of all cases and executes the given function. Propability is a value between 0.0 and 1.0.")]
		protected bool Prop(double propability, Action del)
		{
			if (Prop(propability))
			{
				del();
				return true;
			}
			return false;
		}

		[Doc(Summary = "Applies a linear mapping of 'value', defined by the linear function that goes through P(x1, y1) and P(x2, y2).")]
		protected double Map(double x1, double y1, double x2, double y2, double value)
		{
			return MathHelper.Map(x1, y1, x2, y2, value);
		}

		#endregion

		#region Scheduling

		private readonly Scheduler _scheduler;

		[Doc(Summary = "Executes the given function after the time elapsed by the given bar and note96. If the transport is in loop, TODO")]
		protected void FromNow(int bar, int note96, Action del, bool removeWhenTransportHasStopped = true)
		{
			_scheduler.Schedule(new ScheduleInfo(this, Pos.Add(note96, bar), del, removeWhenTransportHasStopped));
		}

		[Doc(Summary = "Executes the given function after the time elapsed by the given duration. If the transport is in loop, TODO")]
		protected void FromNow(Position duration, Action del, bool removeWhenTransportHasStopped = true)
		{
			_scheduler.Schedule(new ScheduleInfo(this, Pos.Add(duration), del, removeWhenTransportHasStopped));
		}

		#endregion

		#region Voicing

		private VoiceManager _voiceManager;

		[Doc(Summary = "TODO: StopsWhenParentStops - Creates a new voice with the given callback and returns a CallbackControl for that voice. The voice is scheduled by the current sample rate.")]
		protected Voice Voice(Action del, bool stopWhenParentStops = true)
		{
			// Wenn kein AudioPlayback erfolgt ist, kann die SampleRate 0 sein.
			// Dann kommt es zu einer Exception.
			return Voice(v => del());
		}

		[Doc(Summary = "Creates a new voice with the given callback and returns a CallbackControl for that voice. The voice is scheduled by the given sample rate in Hz. Example: '400' means: The callback will run 400 times per second. If the sample rate is higher than the current autio sample rate, the autio smaple rate is used.")]
		protected Voice Voice(double hz, Action del, bool stopWhenParentStops = true)
		{
			return Voice(hz, v => del());
		}

		[Doc(Summary = "Creates a new voice with the given callback and returns a CallbackControl for that voice. The voice is scheduled by the current sample rate. The CallbackControl is passed to the callback function so that it can be accessed from inside the callback.")]
		protected Voice Voice(Action<Voice> del, bool stopWhenParentStops = true)
		{
			// Wenn kein AudioPlayback erfolgt ist, kann die SampleRate 0 sein.
			// Dann kommt es zu einer Exception.
			return Voice(SampleRate == 0 ? 44100 : SampleRate, del);
		}

		[Doc(Summary = "Creates a new voice with the given callback and returns a CallbackControl for that voice. The voice is scheduled by the given sample rate. If the sample rate is higher than the current autio sample rate, the autio smaple rate is used. The CallbackControl is passed to the callback function so that it can be accessed from inside the callback.")]
		protected Voice Voice(double hz, Action<Voice> del, bool stopWhenParentStops = true)
		{
			var voice = new Voice(_voiceManager, SampleRate, hz, del, stopWhenParentStops);
			return voice;
		}

		#endregion

		#region Note96 Callbacks

		private Note96CallbackController _note96CallbackController;

		[Doc(Summary = "Calls the given callback when there is a change in the 96th note. The CallbackControl is passed to the callback function so that it can be accessed from inside the callback.")]
		protected CallbackControl Note96(Action<CallbackControl> callback)
		{
			return _note96CallbackController.Register(callback);
		}

		[Doc(Summary = "Calls the given callback when there is a change in the 96th note.")]
		protected CallbackControl Note96(Action callback)
		{
			return _note96CallbackController.Register(callback);
		}

		#endregion

		#region Connect

		internal Action ConnectCallback { get; private set; }

		[Doc(Summary = "The connect callback is executed each sample cycle after all voices of all patches have been executed. Use this callback to mix voices and route values to the output. There can only be one connect callback specified. The connect callback is executed only on the top-level patch. If a patch acts like sub-patch, the callback is not executed.")]
		protected void Connect(Action callback)
		{
			ConnectCallback = callback;
		}

		#endregion

		#region Key Controller

		private KeyboardCallbackManager _keyboardCallbackManager;

		[Doc(Summary = "Register a key down callback that is called when a key on the computer keyboard is pressed. The given KeyEventArgs is defined here: http://msdn.microsoft.com/de-de/library/system.windows.input.keyeventargs.aspx")]
		protected CallbackControl KeyDown(Action<KeyEventArgs> callback)
		{
			return _keyboardCallbackManager.KeyDownCallbackController.Register(callback);
		}

		[Doc(Summary = "Register a key down callback that is called when a key on the computer keyboard is pressed. The CallbackControl is passed to the callback function so that it can be accessed from inside the callback. The given KeyEventArgs is defined here: http://msdn.microsoft.com/de-de/library/system.windows.input.keyeventargs.aspx")]
		protected CallbackControl KeyDown(Action<KeyEventArgs, CallbackControl> callback)
		{
			return _keyboardCallbackManager.KeyDownCallbackController.Register(callback);
		}

		[Doc(Summary = "Register a key up callback that is called when a key on the computer keyboard is pressed. The given KeyEventArgs is defined here: http://msdn.microsoft.com/de-de/library/system.windows.input.keyeventargs.aspx")]
		protected CallbackControl KeyUp(Action<KeyEventArgs> callback)
		{
			return _keyboardCallbackManager.KeyUpCallbackController.Register(callback);
		}

		[Doc(Summary = "Register a key up callback that is called when a key on the computer keyboard is pressed. The CallbackControl is passed to the callback function so that it can be accessed from inside the callback. The given KeyEventArgs is defined here: http://msdn.microsoft.com/de-de/library/system.windows.input.keyeventargs.aspx")]
		protected CallbackControl KeyUp(Action<KeyEventArgs, CallbackControl> callback)
		{
			return _keyboardCallbackManager.KeyUpCallbackController.Register(callback);
		}

		#endregion

		#region Transport Started / Stopped

		private TransportRegistrar _transportRegistrar;

		[Doc(Summary = "The given callback is executed when the transport has started.")]
		protected void Started(Action callback)
		{
			_transportRegistrar.RegisterStartedCallback(callback);
		}

		[Doc(Summary = "The given callback is executed when the transport has stopped.")]
		protected void Stopped(Action callback)
		{
			_transportRegistrar.RegisterStoppedCallback(callback);
		}

		#endregion

		#region Action and Func

		protected Action Action(Action del) { return del; }
		protected Action<T1> Action<T1>(Action<T1> del) { return del; }
		protected Action<T1, T2> Action<T1, T2>(Action<T1, T2> del) { return del; }
		protected Action<T1, T2, T3> Action<T1, T2, T3>(Action<T1, T2, T3> del) { return del; }
		protected Action<T1, T2, T3, T4> Action<T1, T2, T3, T4>(Action<T1, T2, T3, T4> del) { return del; }
		protected Action<T1, T2, T3, T4, T5> Action<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> del) { return del; }
		protected Action<T1, T2, T3, T4, T5, T6> Action<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> del) { return del; }
		protected Action<T1, T2, T3, T4, T5, T6, T7> Action<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> del) { return del; }
		protected Action<T1, T2, T3, T4, T5, T6, T7, T8> Action<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> del) { return del; }
		protected Action<T1, T2, T3, T4, T5, T6, T7, T8, T9> Action<T1, T2, T3, T4, T5, T6, T7, T8, T9>(Action<T1, T2, T3, T4, T5, T6, T7, T8, T9> del) { return del; }
		protected Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> del) { return del; }

		protected Func<T1> Func<T1>(Func<T1> del) { return del; }
		protected Func<T1, T2> Func<T1, T2>(Func<T1, T2> del) { return del; }
		protected Func<T1, T2, T3> Func<T1, T2, T3>(Func<T1, T2, T3> del) { return del; }
		protected Func<T1, T2, T3, T4> Func<T1, T2, T3, T4>(Func<T1, T2, T3, T4> del) { return del; }
		protected Func<T1, T2, T3, T4, T5> Func<T1, T2, T3, T4, T5>(Func<T1, T2, T3, T4, T5> del) { return del; }
		protected Func<T1, T2, T3, T4, T5, T6> Func<T1, T2, T3, T4, T5, T6>(Func<T1, T2, T3, T4, T5, T6> del) { return del; }
		protected Func<T1, T2, T3, T4, T5, T6, T7> Func<T1, T2, T3, T4, T5, T6, T7>(Func<T1, T2, T3, T4, T5, T6, T7> del) { return del; }
		protected Func<T1, T2, T3, T4, T5, T6, T7, T8> Func<T1, T2, T3, T4, T5, T6, T7, T8>(Func<T1, T2, T3, T4, T5, T6, T7, T8> del) { return del; }
		protected Func<T1, T2, T3, T4, T5, T6, T7, T8, T9> Func<T1, T2, T3, T4, T5, T6, T7, T8, T9>(Func<T1, T2, T3, T4, T5, T6, T7, T8, T9> del) { return del; }
		protected Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> del) { return del; }
		protected Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> del) { return del; }

		#endregion

		[Doc(
			Summary = "TODO: Voicing Hierarchy - Creates a new patch specified by the given type T. Use the #i directive to include other patchces. TODO: Patches are auto-created. When you use this method explicitely, some things wont work because the patch is not registered.",
			Example = "var myPatch = Create&lt;MyPatch&gt;();")]
		protected T Create<T>() where T : Patch
		{
			return (T)Create(typeof(T), null, false);
		}

		private object Create(Type patchType, string memberName, bool registerPatch)
		{
			return _patchBuilder.ResolvePatch(patchType, this, memberName, registerPatch);
		}

		[Doc(Summary = "Logs a message to the log window. This can be used for debugging purpose.")]
		public void Log(object message, bool suppressEqualMessages = false)
		{
			_logger.Log(
				message == null 
					? "null"
					: message is string
						? (string)message
						: message.ToString(),
				suppressEqualMessages);
		}

		internal void Dispose()
		{
			// TODO: Was soll das?
		}
	}
}
